package org.bridgen;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import org.bridgen.lang.parser.BridgenParser;
import org.bridgen.lang.scanner.BridgenScanner;
import org.bridgen.lang.semantics.ast.Grammar;
import org.bridgen.lang.semantics.ast.IError;
import org.bridgen.lang.semantics.ast.IgnoreDecl;
import org.bridgen.lang.semantics.ast.List;
import org.bridgen.lang.semantics.ast.NodeDecl;
import org.bridgen.lang.semantics.ast.Program;

import java_cup.runtime.Symbol;

/**
 * Main class of Bridgit.
 * 
 * Bridge Parser generator. Generates a bridge parser along
 * with classes corresponding to islands and reefs in a 
 * given bridge grammar.
 * 
 * @author Emma Söderberg <emma add cs.lth.se>
 *
 */

public class BridgeParserGenerator {

	public static final String HELP_FLAG = "--help";
	public static final String PARSER_NAME_FLAG = "--parser-name";
	public static final String OUTPUT_DIR_FLAG = "--output-dir";
	private static final String GRAMMAR_PATH_FLAG = "--grammar-path";
	private static final String PACKAGE_FLAG = "--package";
	

	private static void generate(String grammar, String grammarPath, 
			String outputDir, String packageName, String parserName) {

		System.out.println("Output directory [" + outputDir + "]");
		
		// Load main grammar
		Grammar ast = parseGrammarFile(grammar);
		ast.setFromSource(true);

		if (parserName.trim().length() == 0) {
			parserName = ast.getName() + "BridgeParser";
		}
		System.out.println("Parser name [" + parserName + "]");
		System.out.println("Generating for [" + 
				(ast.isSGLRGrammar() ? "SGLR" : "text-based") + "]");
		
		// Create program node
		List grammarList = new List();
		Program program = new Program(grammarList);
		program.prepForAnalysis();
		
		// Load imported grammars
		HashMap<String,Grammar> includedGrammars = new HashMap<String, Grammar>();
		includedGrammars.put(ast.getName(), ast);
		grammarList.add(ast);
		importGrammars(ast, grammarPath, grammarList, includedGrammars);

		// Do a pretty print
		StringBuffer buf = new StringBuffer();
		program.pp(buf);
		System.out.println(buf);
		
		if (checkForSemanticErrors(includedGrammars.values())) {
			generateCode(program, outputDir, packageName, 
					parserName, includedGrammars.values());
		}
	}

	
	private static void generateCode(Program program, String outputDir, 
			String packageName, String parserName, 
			Collection<Grammar> grammarList) {
		boolean forSGLR = false;
		if (packageName.trim().length() > 0) {
			System.out.println("Generating classes to package [" + packageName + "]");
		}
		for (Grammar g : grammarList) {
			if (/*g.isFromSource() && */g.isSGLRGrammar()) {
				forSGLR = true;
			}
			for (Object obj : g.nodeDecls()) {
				NodeDecl decl = (NodeDecl)obj;
				if (!decl.inBaseLibrary())
					generateNodeDecl(decl, outputDir, packageName);
			}
			for (Object obj : g.ignoreDecls()) {
				IgnoreDecl decl = (IgnoreDecl)obj;
				generateIgnoreDecl(decl, outputDir, packageName);
			}
		}
		generateParserCode(program, packageName, parserName, outputDir, forSGLR);
	}
	
	private static void generateParserCode(Program program, String packageName, 
			String parserName, String outputDir, boolean forSGLR) {
		
		if (forSGLR) {
			System.out.println("Generating for [SGLR]");
		}
		
		System.out.print("Creating parser class [" + parserName + "] ");
		String path = outputDir + "/" + parserName + ".java";
		File file = new File(path);
		
		try {
			file.createNewFile();
			System.out.print(".");
			FileWriter out = new FileWriter(file);
			StringBuffer buf = new StringBuffer();
			if (forSGLR) {
				program.generateSGLRParserCode(packageName, parserName, buf);
			} else {
				program.generateParserCode(packageName, parserName, buf);
			}
			System.out.print(".");
			out.write(buf.toString());
			System.out.print(".");
			out.close();
		} catch (FileNotFoundException e) {
			exitPrintMessage("Problem creating parser class : " + e.getMessage());
		} catch (IOException e) {
			exitPrintMessage("Problem creating parser class : " + e.getMessage());
		}
		System.out.println(" done");
	}
	
	private static void generateNodeDecl(NodeDecl decl, 
			String outputDir, String packageName) {
		
		System.out.print("Creating node class [" + decl.getName() + "]");
		String path = outputDir + "/" + decl.getName() + ".java";
		File file = new File(path);
		
		try {
			file.createNewFile();
			System.out.print(".");
			FileWriter out = new FileWriter(file);
			StringBuffer buf = new StringBuffer();
			decl.generateCode(buf, packageName);
			System.out.print(".");
			out.write(buf.toString());
			System.out.print(".");
			out.close();
		} catch (FileNotFoundException e) {
			exitPrintMessage("Problem creating class for node " + 
					decl.getName() + ": " + e.getMessage());
		} catch (IOException e) {
			exitPrintMessage("Problem creating class for node " + 
					decl.getName() + ": " + e.getMessage());
		}
		System.out.println(" done");
	}

	private static void generateIgnoreDecl(IgnoreDecl decl, 
			String outputDir, String packageName) {
		
		System.out.print("Creating ignore class [" + decl.className() + "]");
		String path = outputDir + "/" + decl.className() + ".java";
		File file = new File(path);
		
		try {
			file.createNewFile();
			System.out.print(".");
			FileWriter out = new FileWriter(file);
			StringBuffer buf = new StringBuffer();
			decl.generateCode(buf, packageName);
			System.out.print(".");
			out.write(buf.toString());
			System.out.print(".");
			out.close();
		} catch (FileNotFoundException e) {
			exitPrintMessage("Problem creating class for ignore " + 
					decl.getName() + ": " + e.getMessage());
		} catch (IOException e) {
			exitPrintMessage("Problem creating class for ignore " + 
					decl.getName() + ": " + e.getMessage());
		}
		System.out.println(" done");
	}
		

	private static boolean checkForSemanticErrors(Collection<Grammar> grammarList) {
		for (Grammar g : grammarList) {
			Collection errors = g.errorCheck();
			if (!errors.isEmpty()) {
				printSemanticErrors(errors, g.getName());
				return false;
			}
		}
		return true;
	}


	private static void importGrammars(Grammar ast, String grammarPath, 
			List list, HashMap<String, Grammar> includedGrammars) {
		for (String s : ast.importedGrammars()) {
			if (!includedGrammars.containsKey(s)) {
				String path = grammarPath + "/" + s + ".bp";
				Grammar importedAST = parseGrammarFile(path);
				includedGrammars.put(s, importedAST);
				list.add(importedAST);
				importGrammars(importedAST, grammarPath, list, includedGrammars);
			}
		}
	}

	private static Grammar parseGrammarFile(String grammarFile) {
		Grammar ast = null;
		InputStream in = null;
		System.out.print("Reading in grammar file [" + grammarFile + "] ");
		try {
			in = new FileInputStream(grammarFile);
			System.out.print(".");
			BridgenScanner scanner = new BridgenScanner(in);
			System.out.print(".");
			BridgenParser parser = new BridgenParser(scanner);
			Symbol parseTree = parser.parse();
			System.out.print(".");
			if (!(parseTree.value instanceof Grammar)) {
				throw new Error("Weirdness in grammar file [" + grammarFile + 
						"] : parser returned the wrong top node");
			}
			ast = (Grammar)parseTree.value;
			System.out.println(" ok");
			return ast;
		} catch (IOException e) {
			System.out.println(" failed");
			exitPrintMessage("Problem opening grammar file [" + grammarFile + 
					"] : \"" + e.getMessage() + "\"");	
		} catch (Exception e) {
			System.out.println(" failed");
			exitPrintMessage("Problem parsing grammar file [" + grammarFile + 
					"] : \"" + e.getMessage() + "\"");
		} finally {
			try {
				if (in != null)
					in.close();
			} catch (IOException e) {
				exitPrintMessage(e.getMessage());
			}
		}
		return ast;
	}
	
	public static void main(String[] args) {
		
		printMyPosition();
		
		String grammar = "";
		String grammarPath = "";
		String outputDir = "";
		String parserName = "";
		String packageName = "";

		// No arguments prints the usage text
		if (args.length == 0) {
			exitPrintUsage();
		}
		
		for (int i = 0; i < args.length-1; i++) {
			if (isHelpFlag(args[i])) {
				exitPrintHelp();
			} else if (isParseNameFlag(args[i])) {
				parserName = extractValue(args[i]); 
				exitIfBadParserName(parserName);
			} else if (isOutputDirFlag(args[i])) {
				outputDir = extractValue(args[i]);
				outputDir = exitIfBadOutputDir(outputDir);
			} else if (isGrammarPathFlag(args[i])) {
				grammarPath = extractValue(args[i]);
				grammarPath = exitIfBadGrammarPath(grammarPath); 
			} else if (isPackageFlag(args[i])) {
				packageName = extractValue(args[i]);
			} else {
				exitUnknownFlag(args[i]);
			}
		}
		
		if (isHelpFlag(args[args.length-1])) {
			exitPrintHelp();
		} else {
			grammar = args[args.length-1];
			grammar = exitIfBadFilePath(grammar);
		}
		
		if (outputDir.trim().equals("")) {
			exitPrintMessage("error: Missing output directory");
		}
		
		generate(grammar, grammarPath, outputDir, packageName, parserName);
	}
	
	private static void printMyPosition() {
		File pwdFile = new File(".");
		String pwdPath = "";
		try {
			pwdPath = pwdFile.getCanonicalPath();
			System.out.println("Generators position is [" + pwdPath + "]");
		} catch (IOException e) {
			System.out.println("Cannot show my position : \"" + 
					e.getMessage() + "\"");
			System.exit(4);
		}
		
	}


	private static String extractValue(String arg) {
		String[] res = arg.split("=");
		return res.length > 0 ? res[1] : arg;
	}
	
	private static boolean isParseNameFlag(String value) {
		return value.startsWith(PARSER_NAME_FLAG + "=");
	}

	private static boolean isOutputDirFlag(String value) {
		return value.startsWith(OUTPUT_DIR_FLAG + "=");
	}
	
	private static boolean isGrammarPathFlag(String value) {
		return value.startsWith(GRAMMAR_PATH_FLAG + "=");
		
	}
	
	private static boolean isPackageFlag(String value) {
		return value.startsWith(PACKAGE_FLAG + "=");
	}

	private static boolean isHelpFlag(String value) {
		return value.equals(HELP_FLAG);
	}

	private static void printUsage() {
		System.out.println("usage: bp-gen " + OUTPUT_DIR_FLAG + "=... [" + 
				GRAMMAR_PATH_FLAG + "=...] [" + OUTPUT_DIR_FLAG + "=...] [" + 
				PARSER_NAME_FLAG + "=...] | " + PACKAGE_FLAG + "=...] [" + 
				HELP_FLAG + "] grammar-file.bp");
	}
	
	private static void exitPrintUsage() {
		printUsage();
		System.exit(1);
	}
	
	private static void exitPrintHelp() {
		printUsage();
		System.out.println("\nList of Options:\n" +
				"\t" + PACKAGE_FLAG + "     Use this name as package name\n" +
				"\t" + GRAMMAR_PATH_FLAG + "       Include grammar files from this directory\n" +
				"\t" + OUTPUT_DIR_FLAG + "=...   Put generated classes in the given directory.\n" +
				"\t                   Default is the current directory.\n" + 
				"\t" + PARSER_NAME_FLAG + "=...  Uses this name as class name for the parser\n" +
				"\t                   instead of the module name in the grammar file." + 
				"\t" + HELP_FLAG + "             Prints this help text\n");
		System.exit(2);
	}
	
	private static void exitPrintMessage(String message) {
		System.out.println("error: " + message);
		System.exit(3);
	}
	
	private static void exitIfBadParserName(String parserName) {
		if (parserName.length() == 0) {
			System.out.println("error: Missing parser name");
			System.exit(3);
		}
	}

	private static String exitIfBadFilePath(String value) {
		File file = new File(value);
		if (!file.exists()) {

			File pwdFile = new File(".");
			String pwdPath = "";
			try {
				pwdPath = pwdFile.getCanonicalPath();
				file = new File(pwdPath, file.getPath());
				if (!file.exists()) {
					System.out.println("error: Grammar file does not exist [" + file.getAbsolutePath() + "]");
					System.exit(3);		
				}
				value = file.getAbsolutePath();
				
			} catch (IOException e) {
				System.out.println("error: Cannot find grammar file [" + file.getAbsolutePath()+ "]");
				System.exit(4);
			}
			
		}
		if (!file.canRead()) {
			System.out.println("error: Cannot read from grammar file [" + value + "]");
			System.exit(4);
		}
		if (!file.isFile()) {
			System.out.println("error: Grammar file is a directory [" + value + "]");
			System.exit(4);
		}
		return value;
	}

	private static String exitIfBadOutputDir(String value) {
		File file = new File(value);
		if (!file.exists()) {
			
			File pwdFile = new File(".");
			String pwdPath = "";
			try {
				pwdPath = pwdFile.getCanonicalPath();
				file = new File(pwdPath, file.getPath());
				value = file.getAbsolutePath();
				if (!file.exists()) {
					System.out.println("error: Output directory does not exist [" + value + "]");
					System.exit(3);
				}
				
				
			} catch (IOException e) {
				System.out.println("error: Canot find output directory [" + value + "]");
				System.exit(3);
			}
		}
		if (!file.canRead()) {
			System.out.println("error: Cannot read from output directory [" + value + "]");
			System.exit(4);
		}
		if (!file.isDirectory()) {
			System.out.println("error: Output directory is not a directory [" + value + "]");
			System.exit(3);
		}
		return value;
	}
	
	private static String exitIfBadGrammarPath(String value) {
		File file = new File(value);
		if (!file.exists()) {
			
			File pwdFile = new File(".");
			String pwdPath = "";
			try {
				pwdPath = pwdFile.getCanonicalPath();
				file = new File(pwdPath, file.getPath());
				value = file.getAbsolutePath();
				if (!file.exists()) {
					System.out.println("error: Grammar directory does not exist [" + value + "]");
					System.exit(3);
				}
				
				
			} catch (IOException e) {
				System.out.println("error: Canot find grammar directory [" + value + "]");
				System.exit(3);
			}
		}
		if (!file.canRead()) {
			System.out.println("error: Cannot read from grammar directory [" + value + "]");
			System.exit(4);
		}
		if (!file.isDirectory()) {
			System.out.println("error: Grammar directory is not a directory [" + value + "]");
			System.exit(3);
		}
		return value;
	}

	private  static void exitUnknownFlag(String value) {
		System.out.println("error: Unknown option [" + value + "]");
		printUsage();
		System.exit(2);
	}
	
	private static void printNoSyntaxErrors(String grammarFile) {
		System.out.println("No syntax errors in grammar file [" + 
				grammarFile + "]");	
	}

	private static void printNoSemanticErrors(String grammarFile) {
		System.out.println("No semantic errors in grammar file [" + 
				grammarFile + "]");
	}
	
	private static void printSemanticErrors(Collection errors, String grammar) {
		System.out.println("Semantic errors in grammar [" + grammar + "] :");
		for (Iterator itr = errors.iterator(); itr.hasNext();) {
			IError error = (IError)itr.next();
			System.out.println("\t" + error);
		}
	}
}
